﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Text.RegularExpressions;

namespace VisaValeCheck
{
    [Serializable()]
    public sealed class VisaSaldo
    {
        [XmlElement()]
        public NumeroCartao NumeroCartao { get; set; }
        [XmlAttribute()]
        public double SaldoDisponivel { get; set; }
        [XmlAttribute()]
        public DateTime UltimoDeposito { get; set; }
        [XmlElement()]
        public Compra ProximoDeposito { get; set; }
        [XmlElement()]
        public List<Compra> Compras { get; set; }
        [XmlElement()]
        public List<Compra> Creditos { get; set; }
        //[XmlElement()]
        //public InfoPringles Pringles { get; set; }
                
        public double TotalDespesas
        {
            get
            {
                double soma = Compras.Sum((a) =>
                {
                    return a.Valor;
                });
                return soma;
            }
        }

        public double TotalCreditos
        {
            get
            {
                double soma = Creditos.Sum((a) =>
                {
                    return a.Valor;
                });
                return soma;
            }
        }

        public Compra UltimaCompra
        {
            get
            {
                Compra compra = null;
                try
                {
                    compra = Compras.First();
                }
                catch (Exception)
                {
                }
                return compra;
            }
        }

        public Compra UltimoBeneficio
        {
            get
            {
                Compra credito = null;
                try
                {
                    credito = Creditos.First();
                }
                catch
                {
                }
                return credito;
            }
        }

        private string _extrato;

        public VisaSaldo()
        {
        }

        public VisaSaldo(string extrato, NumeroCartao numeroCartao)
        {
            _extrato = extrato;
            NumeroCartao = numeroCartao;
            Creditos = new List<Compra>();
            Compras = new List<Compra>();
            this.PreencherProp();
        }

        /*public VisaSaldo(string extrato, NumeroCartao numeroCartao, PringlesClass pringles)
            : this(extrato, numeroCartao)
        {
            double precoMedioPringles = pringles.PrecoMedio();
            if (precoMedioPringles > 0)
            {
                Pringles = new InfoPringles(Convert.ToInt32(Math.Floor(this.SaldoDisponivel / precoMedioPringles)), precoMedioPringles);
            }
            else
            {
                Pringles = null;
            }
        }*/

        private bool EhDisponibilizacaoBeneficio(Compra a)
        {
            return Regex.Match(a.Local, @"Disponibiliza[cç][aã]o de Benef[ií]cio", RegexOptions.IgnoreCase).Success;
        }

        public bool AdicionarCompras(VisaSaldo novoSaldo)
        {
            var novas = from no in novoSaldo.Compras
                        where
                        Compras.All((x) => x != no)
                        select no;
            Compras.AddRange(novas);
            Compras.Sort((a, b) => b.Data.Subtract(a.Data).Days);
            this.PadronizarEstabelecimentos();
            return novas.Count() > 0;
        }

        /// <summary>
        /// Necessário para que os nomes dos estabeleciomentos sejam padronizados, pois quando o saldo é atualizado
        /// no mesmo dia da utilização do cartão, o nome do estabeleciomento é cortado em alguns caracteres.
        /// </summary>
        private void PadronizarEstabelecimentos()
        {
            foreach (Compra compra in Compras)
            {
                var compraAux = from x in Compras
                                where
                                Apoio.Jaccard(x.Local, compra.Local).Proximidade > 0.8 &&
                                x.Local.Length > compra.Local.Length
                                select x;
                if (compraAux.Count() > 0)
                {
                    Compra c = compraAux.First();
                    compra.Local = c.Local;
                }
            }
        }

        /// <summary>
        /// Adicionar novos créditos no histórico.
        /// </summary>
        /// <param name="novoSaldo">Créditos carregados do site.</param>
        /// <returns>Verdadeiro se foi adicionado algum crédito.</returns>
        public bool AdicionarCreditos(VisaSaldo novoSaldo)
        {
            var novos = from no in novoSaldo.Creditos
                        where
                        Creditos.All((x) => x != no)
                        select no;
            Creditos.AddRange(novos);
            Creditos.Sort((a, b) => b.Data.Subtract(a.Data).Days);
            return novos.Count() > 0;
        }

        public bool AtualizarValores(VisaSaldo novoSaldo)
        {
            //apenas considerei o ProximoDeposito porque os outros valores já possuem eventos associados.            
            bool ret = ProximoDeposito != novoSaldo.ProximoDeposito;
            UltimoDeposito = novoSaldo.UltimoDeposito;
            SaldoDisponivel = novoSaldo.SaldoDisponivel;
            ProximoDeposito = novoSaldo.ProximoDeposito;
            //Pringles = novoSaldo.Pringles;
            return ret;
        }

        private void PreencherProp()
        {
            Match m;
            string local;
            double valor;
            DateTime data;
            //Saldo
            m = Regex.Match(_extrato, @"Saldo disponível\:.+?R\$\s*(.+?)<\/td>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
            if (!m.Success || m.Groups[1].Value.Trim() == string.Empty)
                SaldoDisponivel = 0;
            else
                SaldoDisponivel = Convert.ToDouble(m.Groups[1].Value);
            //data da disponibilziacao do beneficio
            m = Regex.Match(_extrato, @"Data da última disponibilização do benefício:.*?<td.*?>(\d\d\/\d\d)<\/td>\W*<td.*?>Valor:R\$([\d,]+)<\/td>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
            if (!m.Success)
            {
                UltimoDeposito = DateTime.MinValue;
            }
            else
            {
                UltimoDeposito = this.CriaDataSoComDiaEMes(m.Groups[1].Value);
            }
            m = Regex.Match(_extrato, @"Data da próxima disponibilização do benefício:.*?<td.*?>(\d\d/\d\d)<\/td>\W*<td.*?>Valor:R\$([\d,]+)<\/td>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
            if (!m.Success)
            {
                ProximoDeposito = null;
            }
            else
            {
                valor = Convert.ToDouble(m.Groups[2].Value);
                data = this.CriaProximaDataSoComDiaEMes(m.Groups[1].Value);
                local = "Disponibilização de benefício";
                ProximoDeposito = new Compra(valor, data, local);
            }
            MatchCollection mc = Regex.Matches(_extrato, @"<tr class=""rowTable"">\W*<td.*?>(?<data>.*?)<\/td>\W*<td.*?>(?<local>.*?)<\/td>\W*<td.*?>R\$\s*(?<valor>[\d,]+)", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            foreach (Match x in mc)
            {
                local = x.Groups["local"].Value.Trim();
                valor = Convert.ToDouble(x.Groups["valor"].Value.Trim());
                data = this.CriaDataSoComDiaEMes(x.Groups["data"].Value);
                Compra compra = new Compra(valor, data, local);
                if (this.EhDisponibilizacaoBeneficio(compra))
                    Creditos.Add(compra);
                else
                    Compras.Add(compra);
            }
            Compras.Sort((a, b) => b.Data.Subtract(a.Data).Days);
            Creditos.Sort((a, b) => b.Data.Subtract(a.Data).Days);
        }

        private DateTime CriaDataSoComDiaEMes(string diaMes)
        {
            string[] dataSplit = diaMes.Split('/');
            int dia = Convert.ToInt32(dataSplit[0]);
            int mes = Convert.ToInt32(dataSplit[1]);
            int ano = (mes <= DateTime.Now.Month ? DateTime.Today.Year : (DateTime.Today.Year - 1));
            return new DateTime(ano, mes, dia);
        }

        private DateTime CriaProximaDataSoComDiaEMes(string diaMes)
        {
            string[] dataSplit = diaMes.Split('/');
            int dia = Convert.ToInt32(dataSplit[0]);
            int mes = Convert.ToInt32(dataSplit[1]);
            int ano = (mes >= DateTime.Now.Month ? DateTime.Today.Year : (DateTime.Today.Year + 1));
            return new DateTime(ano, mes, dia);
        }
    }
}
